package display;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Insets;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.swing.BorderFactory;

import display.structure.Belt;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.layout.AxisLabelLayout;
import prefuse.action.layout.AxisLayout;
import prefuse.activity.Activity;
import prefuse.activity.ActivityListener;
import prefuse.controls.ControlAdapter;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.query.NumberRangeModel;
import prefuse.render.AxisRenderer;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.PolygonRenderer;
import prefuse.render.Renderer;
import prefuse.render.ShapeRenderer;
import prefuse.render.AbstractShapeRenderer;
import prefuse.util.ColorLib;
import prefuse.util.DataLib;
import prefuse.util.StrokeLib;
import prefuse.util.ui.UILib;
import prefuse.visual.AggregateItem;
import prefuse.visual.AggregateTable;
import prefuse.visual.EdgeItem;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTable;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.ItemSorter;
import weeny.kdtree.KdTreeUtil;
import weeny.pre.display.BaseUIDisplay;
import weeny.pre.util.Geom;
import weeny.pre.util.IntColor;
import weeny.pre.util.Pre;
import weeny.recycle.MarkerHelper;

@SuppressWarnings("deprecation")
public class CurveDisplay extends BaseUIDisplay {    
    // ------------------------------------------------------------------------
    
    private AggregateTable m_at = null;
    public static String PNT_GROUP = "point";
    public static String MAIN_CURVE = "main_curve_group";
    
    private static String ARC_GROUP = "arc_group";
    private static String AT_GROUP  = "at_Group";
    private static String BAR_GROUP = "bar_group";
    private static String BOX_GROUP = "box_group";
    private static String MAK_GROUP = "mark_group";
    private static String XLABEL_GROUP = "xlab";
    private static String YLABEL_GROUP = "ylab";
    
    private static String CURVE_FIELD = "curve_field";
    
    private Rectangle2D m_dataB = new Rectangle2D.Double();
    private Rectangle2D m_xlabB = new Rectangle2D.Double();
    private Rectangle2D m_ylabB = new Rectangle2D.Double();
    
    private static int BAR_COL = IntColor.CATEGORY_5[4];
    private static int PNT_COL = IntColor.CATEGORY_5[1];
    public static int RED = IntColor.CATEGORY_5[0];
    private static int GREEN = ColorLib.rgb(136, 203, 127);
    public static int BLUE = IntColor.CATEGORY_5[3];
    
    private static int MAK_COL = IntColor.CATEGORY_5[1];
    
    private EdgeItem activeBar = null;
    private MarkerHelper mark = null;
    private String CURVE_X = null;
    private String CURVE_Y = null;
    private VisualItem fillingArea = null;
    private EdgeItem movingBar = null;
    private Belt belt = null;
    private List<EdgeItem> arcs = new ArrayList<EdgeItem>();
    private List<Table> rawTables = null;
    public CurveDisplay(final List<Table> tableList, String CURVE_X, String CURVE_Y, Belt belt){
        super(new Visualization());
        this.belt = belt;
        this.rawTables = tableList;
        this.CURVE_X = CURVE_X;
        this.CURVE_Y = CURVE_Y;

        getVisualization().addFocusGroup(PNT_GROUP);
        m_vis.addFocusGroup(BAR_GROUP);
        m_vis.addFocusGroup(BOX_GROUP);
        m_vis.addFocusGroup(MAK_GROUP);
        m_vis.addFocusGroup(ARC_GROUP);
        mark = new MarkerHelper(m_vis, false);
        activeBar = mark.markEdge(BAR_GROUP, Geom.PNT_ZERO, Geom.PNT_ZERO, BAR_COL);
        movingBar = mark.markEdge(BAR_GROUP, Geom.PNT_ZERO, Geom.PNT_ZERO, MAK_COL);
        for(int i = 0; i<10; i++){
        	EdgeItem arc = mark.markArrow(ARC_GROUP, Geom.PNT_ZERO, Geom.PNT_ZERO, MAK_COL);
        	arc.setVisible(false);
        	arcs.add(arc);
        }
		m_vis.getFocusGroup(MAK_GROUP).addTuple(movingBar);
		movingBar.setStroke(StrokeLib.getStroke(4));
		
        m_at = m_vis.addAggregates(AT_GROUP);

        final Display m_display = this;
        // filter to show only candidates receiving more than $100,000
        for(int i = 0; i<tableList.size(); i++){
        	Table table = tableList.get(i);
        	final VisualTable vt = m_vis.addTable(table.hashCode()+"", table);
        	vt.addColumn(CURVE_FIELD, AggregateItem.class);
            for(Iterator<Tuple> it = vt.tuples(); it.hasNext();){
            	getVisualization().getFocusGroup(PNT_GROUP).addTuple(it.next());
            }
            m_at.addColumns(PolygonRenderer.POLYGON_SCHEMA);
            AggregateItem curveItem = (AggregateItem) m_at.addItem();
            if (tableList.size() == 1) {
            	curveItem.setStrokeColor(GREEN);	
			}
            else {
            	if (i == 0) {
            		curveItem.setStrokeColor(RED);
				}
            	else{
            		curveItem.setStrokeColor(BLUE);
            	}
            }
            
            curveItem.setStroke(StrokeLib.getStroke(4,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
            for(Iterator<VisualItem> it = vt.tuples(); it.hasNext();){
            	VisualItem item = it.next();
            	item.set(CURVE_FIELD, curveItem);
            	curveItem.addItem(item);
            }
		}
        AbstractShapeRenderer sr = new ShapeRenderer();
        PolygonRenderer boxRenderer = new PolygonRenderer(){
        	Rectangle2D rect = new Rectangle2D.Double();
        	@Override
        	protected Shape getRawShape(VisualItem item) {
        		setClosePath(true);
        		rect.setFrame(item.getStartX(), m_dataB.getMinY(), 0, 0);
        		rect.add(item.getStartX(), m_dataB.getMaxY());
        		rect.add(item.getEndX(), m_dataB.getMinY());
        		item.set(PolygonRenderer.POLYGON, MarkerHelper.toPolyArray(rect));
        		return super.getRawShape(item);
        	}
        };
        PolygonRenderer pRenderer = new PolygonRenderer(Constants.POLY_TYPE_CURVE){
        	@Override
        	protected Shape getRawShape(VisualItem item) {
        		if (item instanceof AggregateItem) {
            		setClosePath(false);
            		AggregateItem aItem = (AggregateItem) item;
            		ArrayList<VisualItem> items = new ArrayList<VisualItem>();
            		for(Iterator<VisualItem> it = aItem.items(); it.hasNext();){
            			items.add(it.next());
            		}
            		Collections.sort(items, new Comparator<VisualItem>(){
    					public int compare(VisualItem o1, VisualItem o2) {
    						return (int) Math.signum(o1.getX()-o2.getX());
    					}
            		});
            		float[] array = (float[]) item.get(PolygonRenderer.POLYGON);
            		if (array == null || array.length != items.size()*2) {
    					array = new float[items.size()*2];
    				}
            		for(int i = 0; i<items.size(); i++){
            			array[i*2+0] = (float) items.get(i).getX();
            			array[i*2+1] = (float) items.get(i).getY();
            		}
            		item.set(PolygonRenderer.POLYGON, array);
            		return super.getRawShape(item);
				}
        		else{
        			setClosePath(true);
        			return super.getRawShape(item);
        		}
        	}
        };
        EdgeRenderer barRenderer = new EdgeRenderer(){
        	@Override
        	protected Shape getRawShape(VisualItem item) {
        		setArrowType(Constants.EDGE_ARROW_NONE);
        		EdgeItem edge = (EdgeItem) item;
        		Pre.setY(edge.getSourceItem(), m_dataB.getMinY());
        		Pre.setY(edge.getTargetItem(), m_dataB.getMaxY());
        		return super.getRawShape(item);
        	}
        };
        Renderer arY = new AxisRenderer(Constants.RIGHT, Constants.TOP);
        Renderer arX = new AxisRenderer(Constants.CENTER, Constants.FAR_BOTTOM);
        DefaultRendererFactory drf = new DefaultRendererFactory(sr);
        drf.add(new InGroupPredicate("FF"), new PolygonRenderer(Constants.EDGE_TYPE_CURVE));
        drf.add(new InGroupPredicate(AT_GROUP), pRenderer);
        drf.add(new InGroupPredicate(BAR_GROUP), barRenderer);
        drf.add(new InGroupPredicate(BOX_GROUP), boxRenderer);
        drf.add(new InGroupPredicate(XLABEL_GROUP), arX);
        drf.add(new InGroupPredicate(YLABEL_GROUP), arY);
        EdgeRenderer ren = new EdgeRenderer(Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD);
        drf.add(new InGroupPredicate(ARC_GROUP), ren);
        m_vis.setRendererFactory(drf);
        // --------------------------------------------------------------------
        // STEP 2: create actions to process the visual data

        // set up dynamic queries, search set
        
        // set up the actions
        AxisLayout xaxis = new AxisLayout(PNT_GROUP, CURVE_X, Constants.X_AXIS, null); 
        AxisLayout yaxis = new AxisLayout(PNT_GROUP, CURVE_Y, Constants.Y_AXIS, null);
        //yaxis.setScale(Constants.LOG_SCALE);
        double maxY = DataLib.max(m_vis.getFocusGroup(PNT_GROUP), CURVE_Y).getDouble(CURVE_Y);
        yaxis.setRangeModel(new NumberRangeModel(0, maxY, 0, maxY));
        xaxis.setLayoutBounds(m_dataB);
        yaxis.setLayoutBounds(m_dataB);
        
        AxisLabelLayout ylabels = new AxisLabelLayout("ylab", yaxis, m_ylabB);
        AxisLabelLayout xlabels = new AxisLabelLayout("xlab", xaxis, m_xlabB, 15);
        m_vis.putAction("xlabels", xlabels);
        
        m_vis.setValue(PNT_GROUP, null, VisualItem.FILLCOLOR, ColorLib.setAlpha(PNT_COL, 128));
        m_vis.setValue(PNT_GROUP, null, VisualItem.STROKECOLOR, PNT_COL);
        m_vis.setValue(PNT_GROUP, null, VisualItem.SHAPE, Constants.SHAPE_ELLIPSE);
        
        // dems = blue, reps = red, other = gray
        
        ActionList draw = new ActionList();
        draw.add(xaxis);
        draw.add(yaxis);
        draw.add(ylabels);
        draw.add(new RepaintAction());
        m_vis.putAction("draw", draw);

        ActionList update = new ActionList();
        update.add(xaxis);
        update.add(yaxis);
        update.add(ylabels);
        update.add(new RepaintAction());
        m_vis.putAction("update", update);
        update.addActivityListener(new ActivityListener() {
			public void activityStepped(Activity a) {}
			public void activityStarted(Activity a) {}
			public void activityScheduled(Activity a) {}
			public void activityFinished(Activity a) {
				System.out.println("update action finished");
				Iterator<VisualItem> pntIter = m_vis.items(PNT_GROUP);
		    	kdtree = new KdTreeUtil(){
		    		@Override
		    		protected double[] toArray(Object node) {
		    			if (node instanceof Double) {
							Double new_name = (Double) node;
							return new double[]{new_name};
						}
		    			else if (node instanceof EdgeItem){
		    				return new double[]{((EdgeItem) node).getSourceItem().getX()};
		    			}
		    			return new double[]{((VisualItem) node).getX()};
		    		}
		    	};
		    	kdtree.addIterator(pntIter, true);
		    	if (tableList.size() == 2) {
					float[] array = new float[(2*tableList.get(0).getTupleCount()+6+3)*2];
					VisualTable vv = (VisualTable) m_vis.getVisualGroup(tableList.get(0).hashCode()+"");
					int idx = 0;
					for(Iterator<VisualItem> it = vv.tuples(); it.hasNext();){
						VisualItem vi = it.next();
						if (vi.getRow() == 0) {
							array[idx++] = (float) vi.getX();
							array[idx++] = (float) vi.getY();
							array[idx++] = (float) vi.getX();
							array[idx++] = (float) vi.getY();
							array[idx++] = (float) vi.getX();
							array[idx++] = (float) vi.getY();
						}
						array[idx++] = (float) vi.getX();
						array[idx++] = (float) vi.getY();
						if (vi.getRow() == vv.getTupleCount()-1) {
							array[idx++] = (float) vi.getX();
							array[idx++] = (float) vi.getY();
							array[idx++] = (float) vi.getX();
							array[idx++] = (float) vi.getY();
							array[idx++] = (float) vi.getX();
							array[idx++] = (float) vi.getY();
						}
					}
					vv = (VisualTable) m_vis.getVisualGroup(tableList.get(1).hashCode()+"");
					for(int i = vv.getTupleCount()-1; i>=0; i--){
						VisualItem vi = vv.getItem(i);
						array[idx++] = (float) vi.getX();
						array[idx++] = (float) vi.getY();
						if (vi.getRow() == 0) {
							array[idx++] = (float) vi.getX();
							array[idx++] = (float) vi.getY();
							array[idx++] = (float) vi.getX();
							array[idx++] = (float) vi.getY();
							array[idx++] = (float) vi.getX();
							array[idx++] = (float) vi.getY();
						}
					}
					if(fillingArea == null){
						fillingArea = mark.markPoly("FF", array, GREEN, 1);
					}
					else{
						fillingArea.set(PolygonRenderer.POLYGON, array);
					}
					fillingArea.setFillColor(GREEN);
					m_vis.repaint();
		    	}
		    	
			}
			public void activityCancelled(Activity a) {}
		});
        

        
        m_vis.setVisible(PNT_GROUP, null, false);
        
        m_display.addControlListener(new ControlAdapter(){
        	private Point2D abs = new Point2D.Double();
        	@Override
        	public void mouseMoved(MouseEvent e) {
        		m_display.getAbsoluteCoordinate(e.getPoint(), abs);
        		if (!m_dataB.contains(abs)) {
        			if (abs.getX()>m_dataB.getMaxX()) {
						abs.setLocation(m_dataB.getMaxX(), 0);
					}
        			else if (abs.getX()<m_dataB.getMinX()){
        				abs.setLocation(m_dataB.getMinX(), 0);
        			}
				}
        		Pre.setX(activeBar.getSourceItem(), abs.getX());
        		Pre.setX(activeBar.getTargetItem(), abs.getX());

        		m_vis.setVisible(PNT_GROUP, null, false);
        		activeCurveItem().setVisible(true);
        		
        		m_vis.repaint();
        	}
        	@Override
        	public void itemMoved(VisualItem item, MouseEvent e) {
        		mouseMoved(e);
        	}
        });
//        m_display.addControlListener(
//        		new ControlAdapter(){
//        	private Point2D abs = new Point2D.Double();
//        	@Override
//        	public void mouseMoved(MouseEvent e) {
//        		m_display.getAbsoluteCoordinate(e.getPoint(), abs);
//        		if (!m_dataB.contains(abs)) {
//        			if (abs.getX()>m_dataB.getMaxX()) {
//						abs.setLocation(m_dataB.getMaxX(), 0);
//					}
//        			else if (abs.getX()<m_dataB.getMinX()){
//        				abs.setLocation(m_dataB.getMinX(), 0);
//        			}
//				}
//        		Pre.setX(activeBar.getSourceItem(), abs.getX());
//        		Pre.setX(activeBar.getTargetItem(), abs.getX());
//
//        		m_vis.setVisible(PNT_GROUP, null, false);
//        		activeCurveItem().setVisible(true);
//        		
//        		m_vis.repaint();
//        	}
//        	@Override
//        	public void itemMoved(VisualItem item, MouseEvent e) {
//        		mouseMoved(e);
//        	}
//        	private VisualItem currentBox = null;
//        	private VisualItem newBox(){
//                VisualItem box = mark.markBox(BOX_GROUP, new Rectangle2D.Double(), BOX_COL, 1);
//                box.setFillColor(ColorLib.setAlpha(BOX_COL, 128));
//                return box;
//        	}
//        	@Override
//        	public void mouseDragged(MouseEvent e) {
//        		if (currentBox == null){
//        			currentBox = newBox();
//        		}
//        		currentBox.setStartX(activeBar.getSourceItem().getX());
//        		m_display.getAbsoluteCoordinate(e.getPoint(), abs);
//        		currentBox.setEndX(abs.getX());
//        		m_vis.repaint();
//        	}
//        	@Override
//        	public void itemReleased(VisualItem item, MouseEvent e) {
//        		mouseReleased(e);
//        	}
//        	@Override
//        	public void mouseReleased(MouseEvent e) {
//        		currentBox = null;
//        	}
//        	@Override
//        	public void itemDragged(VisualItem item, MouseEvent e) {
//        		mouseDragged(e);
//        	}
//        	
//        	@Override
//        	public void itemEntered(VisualItem item, MouseEvent e) {
//        		if (item == activeBar) {
//        			return;
//        		}
//        		if (item.isInGroup(BAR_GROUP) || item.isInGroup(BOX_GROUP)){
//        			Display d = (Display)e.getSource();
//        	        d.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
//				}
//        	}
//        	@Override
//        	public void itemExited(VisualItem item, MouseEvent e) {
//    			Display d = (Display)e.getSource();
//    	        d.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
//        	}
//        	@Override
//        	public void itemClicked(VisualItem item, MouseEvent e) {
//        		if (item.isInGroup(BOX_GROUP)){
//        			m_vis.getFocusGroup(BOX_GROUP).removeTuple(item);
//        			mark.removeTableItem(item);
//        			m_display.damageReport();
//        			m_vis.repaint();
//        		}
//        		else if (item == activeBar) {
//        				mouseClicked(e);
//					}
//        		else if (item.isInGroup(BAR_GROUP)){
//        			m_vis.getFocusGroup(BAR_GROUP).removeTuple(item);
//        			m_vis.getFocusGroup(MAK_GROUP).removeTuple(item);
//        			mark.removeEdge(item);
//        			m_vis.repaint();
//        		}
//        	}
//        	@Override
//        	public void mouseClicked(MouseEvent e) {
//        		if (e.isAltDown()) {
//					Set<Point2D> set = selectedPnt();
//					for (Point2D point2D : set) {
//						System.out.println(point2D);
//					}
//					return;
//				}
//        		m_display.getAbsoluteCoordinate(e.getPoint(), abs);
//        		if (!m_dataB.contains(abs)) {
//        			if (abs.getX()>m_dataB.getMaxX()) {
//						abs.setLocation(m_dataB.getMaxX(), 0);
//					}
//        			else if (abs.getX()<m_dataB.getMinX()){
//        				abs.setLocation(m_dataB.getMinX(), 0);
//        			}
//				}
//        		EdgeItem edge = mark.markEdge(BAR_GROUP, Pre.PNT_ZERO, Pre.PNT_ZERO, MAK_COL);
//        		m_vis.getFocusGroup(MAK_GROUP).addTuple(edge);
//        		edge.setStroke(StrokeLib.getStroke(4));
//        		Pre.setX(edge.getSourceItem(), abs.getX());
//        		Pre.setX(edge.getTargetItem(), abs.getX());
//        		m_vis.repaint();
//        	}
//        });
        m_display.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
        m_display.setSize(1500,450);
        m_display.setHighQuality(true);
        m_display.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
                displayLayout();
            }
        });
        displayLayout();
        
        m_display.setItemSorter(new ItemSorter(){
        	@Override
        	public int score(VisualItem item) {
        		int score = super.score(item);
        		if (item.isInGroup("FF")) {
					score = 0;
				}
        		else if (item == activeBar) {
					score = 1;
				}
        		return score;
        	}
        });
        m_vis.run("draw");
        m_vis.run("xlabels");
        
        UILib.setColor(this, ColorLib.getColor(255,255,255), Color.GRAY);
    }
    
    public void wheelSetBar(double frac){
    	VisualItem oldCurveItem = (VisualItem) kdtree.nearest(movingBar);
    	double x = (m_dataB.getMaxX()-m_dataB.getMinX())*frac+m_dataB.getMinX();
    	Pre.setX(movingBar.getSourceItem(), x);
    	Pre.setX(movingBar.getTargetItem(), x);
    	VisualItem newCurveItem = (VisualItem) kdtree.nearest(movingBar);
    	if(oldCurveItem != newCurveItem){
    		oldCurveItem.setVisible(false);
    		newCurveItem.setVisible(true);
    		//need to draw the arcs here
    		int[] idxs = (int[]) belt.bagTable.get(newCurveItem.getRow(), Belt.SIMILAR_IDX);
    		double[] vals = (double[]) belt.bagTable.get(newCurveItem.getRow(), Belt.SIMILAR_VAL);
    		for(int i = 0; i<arcs.size(); i++){
    			if(i<idxs.length){
    				EdgeItem arc = arcs.get(i);
    				arc.setVisible(true);
    				Pre.setPos(arc.getSourceItem(), newCurveItem.getX(),m_dataB.getMinY());
    				VisualTable vt = (VisualTable) m_vis.getGroup(rawTables.get(0).hashCode()+"");
    				Pre.setPos(arc.getTargetItem(), vt.getItem(idxs[i]));
    				arc.setSize(vals[i]);
    			}
    			else{
    				arcs.get(i).setVisible(false);
    			}
    		}
    	}
    	m_vis.repaint();
    }
    public void displayLayout() {
        Insets i = getInsets();
        int w = getWidth();
        int h = getHeight();
        int iw = i.left+i.right;
        int ih = i.top+i.bottom;
        int aw = 15;
        int ah = 15;
//        curveItem.setValidated(false);
        m_dataB.setRect(i.left, i.top, w-iw-aw, h-ih-ah);
        m_xlabB.setRect(i.left, h-ah-i.bottom, w-iw-aw, ah-10);
        m_ylabB.setRect(i.left, i.top, w-iw, h-ih-ah);
        
        m_vis.run("update");
        m_vis.run("xlabels");
    }
    private KdTreeUtil kdtree = null;

    public VisualItem activeCurveItem(){
    	return (VisualItem) kdtree.nearest(activeBar);
    }
    public Point2D activePnt(){
    	Point2D result = curvePnt(activeCurveItem());
    	System.out.println("[INFO] active Point is "+result);
    	return result;
    }
    public Set<Point2D> selectedPnt(){
    	Set<Point2D> result = new HashSet<Point2D>();
    	Iterator<EdgeItem> verticalMarkerIter = m_vis.items(MAK_GROUP);
    	while (verticalMarkerIter.hasNext()) {
    		EdgeItem barMark = verticalMarkerIter.next();
    		VisualItem item = (VisualItem) kdtree.nearest(barMark);
    		result.add(curvePnt(item));
    	}
    	return result;
    }
    public static class Range{
    	public final Point2D from;
    	public final Point2D to;
    	public Range(Point2D from, Point2D to) {
    		this.from = from;
    		this.to = to;
		}
    }
    public Set<Range> selectedRange(){
    	Set<Range> result = new HashSet<Range>();
    	Iterator<VisualItem> boxIter = m_vis.items(BOX_GROUP);
    	while (boxIter.hasNext()){
    		VisualItem box = boxIter.next();
    		VisualItem from = (VisualItem) kdtree.nearest(new Double(box.getStartX()));
    		VisualItem to   = (VisualItem) kdtree.nearest(new Double(box.getEndX()));
    		result.add(new Range(curvePnt(from), curvePnt(to)));
    	}
    	return result;
    }
    private Point2D curvePnt(VisualItem item){
    	return new Point2D.Double(
				item.getDouble(CURVE_X),
				item.getDouble(CURVE_Y)
				);
    }
}